using System.Collections.Concurrent;
using System.Data;
using System.Data.SqlClient;
using System.Reflection;
using System.Text;
using Dapper;
using Dapper.Contrib.Extensions;
using DapperExtensions;
using DapperExtensions.Sql;
using KL.Standard.Model.Attribute;
using KL.Standard.Model.Infrastructure;
using KL.Standard.Repositories.DBContext;
using KL.Standard.Repositories.MutliDataSource;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;

namespace KL.Standard.Repositories;

public class BaseRepository<T,K> : IRepository<T> where T : class
{
    #region fields
    public ILogger<BaseRepository<T, K>> Logger { get; set; }

    private string _id;
    public string GetID()
    {
        return this._id;
    }



    /// <summary>
    /// 数据库上下文
    /// </summary>
    public  MyDbContext MyDbContext { set; get; }
    // {
    //     
    //     get
    //     {
    //         if (factory == null)
    //         {
    //             throw  new Exception("in the class[BaseRepository] get MutilInstanceFactory instance is null!");
    //
    //         }
    //         string dataSourceName = typeof(K).Name.Replace("DataSource", "");
    //         return factory.GetService<MyDbContext>(dataSourceName);
    //         //return null;
    //     }
    // }

    #region   事务
    protected IDbTransaction _dbTran;

    protected virtual IDbTransaction DbTransaction(string tenantId)
    {
        return MyDbContext.GetDbTransactionByTenantId(tenantId);
    }

    #endregion


    #region 链接

    protected virtual IDbConnection DbConn
    {
        get
        {
            var tenantId = typeof(K).Name.Replace("DataSource", "");
            return MyDbContext.DbConnection(tenantId);
        }
    }
    #endregion 
    #endregion

    #region TableName

    private static readonly ConcurrentDictionary<RuntimeTypeHandle, string> TypeTableName = new ConcurrentDictionary<RuntimeTypeHandle, string>();
    public static SqlMapperExtensions.TableNameMapperDelegate TableNameMapper;
    protected static string GetTableName(Type type)
    {
        string name;
        if (TypeTableName.TryGetValue(type.TypeHandle, out name)) return name;

        if (TableNameMapper != null)
        {
            name = TableNameMapper(type);
        }
        else
        {
            //NOTE: This as dynamic trick should be able to handle both our own Table-attribute as well as the one in EntityFramework 
            var tableAttr = type
                .GetCustomAttributes(false).SingleOrDefault(attr => attr.GetType().Name == "TableAttribute") as dynamic;
            if (tableAttr != null)
                name = tableAttr.Name;
            else
            {
                name = type.Name + "s";
                if (type.IsInterface && name.StartsWith("I"))
                    name = name.Substring(1);
            }
        }

        TypeTableName[type.TypeHandle] = name;
        return name;
    }

    protected static string GetTableName()
    {
        var type = typeof(T);
        return GetTableName(type);
    }

    protected string Query(string columns = null)
    {
        return $"select {columns ?? "*"} from {GetTableName()} where 1=1 ";
    }
    protected string AdminQuery(string columns = null)
    {
        return $"select {columns ?? "*"} from {GetTableName()} where 1=1 ";
    }

    protected string Count()
    {
        return $"select count(*) from {GetTableName()} where 1=1 ";
    }

    protected string Delete()
    {
        return $"delete from {GetTableName()} where 1=1 ";
    }
    #endregion

    public BaseRepository()
    {
        
        //CacheKey += GetTableName() + ".";
        this._id = Guid.NewGuid().ToString();
    }

    #region CRUD

    public bool Delete(T obj, IDbTransaction trans = null)
    {
        RemoveCache(obj);
        return DapperExtensions.DapperExtensions.Delete(DbConn, obj, trans);
    }

    public T Get(long id)
    {
        return DapperExtensions.DapperExtensions.Get<T>(DbConn, id);
    }

    public virtual long Save(T obj, IDbTransaction trans = null)
    {
        return 0;
    }

    public long Insert(T obj, IDbTransaction trans = null)
    {
        return DapperExtensions.DapperExtensions.Insert(DbConn, obj, trans);
    }

    public bool Update(T obj, IDbTransaction trans = null)
    {
        //obj.updatedTime = DateTime.Now;
        return DapperExtensions.DapperExtensions.Update(DbConn, obj, trans);
    }

    


    #endregion

    #region New CRUD

    #region  按照条件查询出分页结果集合（适用于视图或者单表）
    /// <summary>
    /// 按照条件查询出分页结果集合（适用于视图或者单表）
    /// </summary>
    /// <param name="search"></param>
    /// <param name="tableName"></param>
    /// <param name="whereSql"></param>
    /// <param name="param"></param>
    /// <param name="commandTimeout"></param>
    /// <typeparam name="K"></typeparam>
    /// <returns></returns>
    public async Task<PageResult<K>> GetPageAsync<K>(PageSearchModel2 search, string tableName = null,
        string whereSql = null,
        Object param = null, int? commandTimeout = 30)
    {
        var type = typeof(K);
        var properties = type.GetProperties();
        StringBuilder sb = new StringBuilder(" select ");
        var fields = new List<string>();
        foreach (var propertyInfo in properties)
        {
            var attribute = propertyInfo.GetCustomAttributes<DbFieldAttribute>().FirstOrDefault();
            if (attribute != null && attribute.isIgnore4Select)
                continue;
            if (attribute != null && !string.IsNullOrEmpty(attribute.selectChangeType))
            {
                fields.Add($" cast( {propertyInfo.Name} as {attribute.selectChangeType}) as {propertyInfo.Name} ");
            }
            else
            {
                fields.Add($" {propertyInfo.Name} ");
            }
        }

        sb.Append(string.Join(" , ", fields));
        sb.Append($" from {tableName ?? GetTableName(typeof(K))} ");
        if (!string.IsNullOrEmpty(whereSql))
            sb.Append($" where {whereSql} ");
        var countSqlstr = sb.ToString();

        var vSql = sb.ToString();


        var pager = new PageResult<K>();
        var countSql = $"Select Count(1) from ({countSqlstr}) c";
        pager.totalCount = await DbConn.QueryFirstOrDefaultAsync<int>(countSql, param);

        vSql +=
            $" order by {search.Sort} {search.Order} OFFSET {search.Offset} ROWS FETCH NEXT {search.Limit} ROWS ONLY";
        Logger.LogDebug($"GetPageAsync vSql : {vSql}");
        pager.rows = await DbConn.QueryAsync<K>(vSql, param, commandTimeout: commandTimeout);

        return pager;
    }
    #endregion
    
    #region 按照条件查询出列表
    /// <summary>
    /// 按照条件查询出列表
    /// </summary>
    /// <param name="tableName"></param>
    /// <param name="whereSql"></param>
    /// <param name="orderSql"></param>
    /// <param name="param"></param>
    /// <param name="commandTimeout"></param>
    /// <typeparam name="K"></typeparam>
    /// <returns></returns>
    public Task<IEnumerable<K>> GetListAsync<K>(string tableName=null, string whereSql=null, string orderSql=null,
        Object param=null, int? commandTimeout = 30)
    {
        var type = typeof(K);
        var properties = type.GetProperties();
        StringBuilder sb = new StringBuilder(" select ");
        var fields = new List<string>();
        foreach (var propertyInfo in properties)
        {
            var attribute =  propertyInfo.GetCustomAttributes<DbFieldAttribute>().FirstOrDefault();
            if(attribute!=null&& attribute.isIgnore4Select)
                continue;
            if (attribute != null && !string.IsNullOrEmpty(attribute.selectChangeType))
            {
                fields.Add($" cast( {propertyInfo.Name} as {attribute.selectChangeType}) as {propertyInfo.Name} ");
            }
            else
            {
                fields.Add($" {propertyInfo.Name} ");
            }
        }

        sb.Append(string.Join(" , ", fields));
        sb.Append($" from {tableName ?? GetTableName(typeof(K))} ");
        if (!string.IsNullOrEmpty(whereSql))
            sb.Append($" where {whereSql} ");
        if (!string.IsNullOrEmpty(orderSql))
            sb.Append($@" order by {orderSql} ");
        var vSql = sb.ToString();
        Logger.LogDebug($"GetDatasAsync vSql : {vSql}");
        
        return DbConn.QueryAsync<K>(vSql, param,  commandTimeout:commandTimeout);
    }
    #endregion

    #region 查询某一条明细

    /// <summary>
    /// 查询某一条明细
    /// </summary>
    /// <param name="tableName"></param>
    /// <param name="whereSql"></param>
    /// <param name="orderSql"></param>
    /// <param name="param"></param>
    /// <param name="commandTimeout"></param>
    /// <typeparam name="K"></typeparam>
    /// <returns></returns>
    public Task<K> GetFirstOrDefaultAsync<K>(string tableName = null, string whereSql = null,
        string orderSql = null,
        Object param = null, int? commandTimeout = 30)
    {
        var type = typeof(K);
        var properties = type.GetProperties();
        StringBuilder sb = new StringBuilder(" select ");
        var fields = new List<string>();
        foreach (var propertyInfo in properties)
        {
            var attribute = propertyInfo.GetCustomAttributes<DbFieldAttribute>().FirstOrDefault();
            if (attribute != null && attribute.isIgnore4Select)
                continue;
            if (attribute != null && !string.IsNullOrEmpty(attribute.selectChangeType))
            {
                fields.Add($" cast( {propertyInfo.Name} as {attribute.selectChangeType}) as {propertyInfo.Name} ");
            }
            else
            {
                fields.Add($" {propertyInfo.Name} ");
            }
        }

        sb.Append(string.Join(" , ", fields));
        sb.Append($" from {tableName ?? GetTableName(typeof(K))} ");
        if (!string.IsNullOrEmpty(whereSql))
            sb.Append($" where {whereSql} ");
        if (!string.IsNullOrEmpty(orderSql))
            sb.Append($@" order by {orderSql} ");
        var vSql = sb.ToString();
        Logger.LogDebug($"GetDatasAsync vSql : {vSql}");

        return DbConn.QueryFirstOrDefaultAsync<K>(vSql, param, commandTimeout: commandTimeout);
    }

    #endregion
    
    #region 异步保存实例
    /// <summary>
    /// 保存实例
    /// </summary>
    /// <param name="obj"></param>
    /// <param name="chkUp">为修改时的条件</param>
    /// <param name="UpSql">修改的sql</param>
    /// <param name="AddSql">新增的sql</param>
    /// <param name="trans">事务</param>
    /// <typeparam name="TP"></typeparam>
    /// <returns></returns>
    public async  Task<int> SaveAsync<TP>(TP obj,Func<TP,bool> chkUp,string UpSql = null,string AddSql = null, IDbTransaction trans = null) where TP:class
    {
        if (chkUp(obj))//update
        {
            if (!string.IsNullOrEmpty(UpSql))
            {
                return await this.DbConn.ExecuteAsync(UpSql, obj, trans);
            }
            else
            {
                var flag = await SqlMapperExtensions.UpdateAsync<TP>(DbConn, obj, trans);
                return flag ? 1 : 0;
            }
               
        }
        else
        {
            if (!string.IsNullOrEmpty(AddSql))
            {
                return await this.DbConn.ExecuteAsync(AddSql, obj, trans);
            }
            else
            {
                await SqlMapperExtensions.InsertAsync<TP>(this.DbConn, obj);
                return 1;
            }
                
        }
    }
    #endregion

    #region 使用sqlbulk來實現批量導入(需要將dapper從2.04升級到2.09才能netcore版本)

    /// <summary>
    /// 使用sqlbulk來實現批量導入(需要將dapper從2.04升級到2.09才能netcore版本)
    /// </summary>
    /// <param name="entityList"></param>
    /// <param name="trans"></param>
    /// <param name="filter">过滤哪些栏位可以不用于映射</param>
    /// <typeparam name="K"></typeparam>
    /// <returns></returns>
    // public void BatchInsert4Bulk<K>(List<K> entityList, IDbTransaction trans, Func<IPropertyMap, bool> filter)
    // {
    //     var tblName = GetTableName(typeof(K));
    //     //var tran = trans;
    //     using (var bulkCopy =
    //            new SqlBulkCopy(this.DbContext.dbConnectionstr, SqlBulkCopyOptions.UseInternalTransaction))
    //     {
    //         bulkCopy.BatchSize = entityList.Count();
    //         bulkCopy.BulkCopyTimeout = 300;
    //         bulkCopy.DestinationTableName = tblName;
    //         var table = new DataTable();
    //         DapperExtensions.Sql.ISqlGenerator sqlGenerator = new SqlGeneratorImpl(new DapperExtensionsConfiguration());
    //         var classMap = sqlGenerator.Configuration.GetMap<T>();
    //         var props = classMap.Properties.Where(x => x.Ignored == false).ToArray();
    //         props = props.Where(i => !filter(i)).ToArray();
    //         foreach (var propertyInfo in props)
    //         {
    //             bulkCopy.ColumnMappings.Add(propertyInfo.Name, propertyInfo.Name);
    //             table.Columns.Add(propertyInfo.Name,
    //                 Nullable.GetUnderlyingType(propertyInfo.PropertyInfo.PropertyType) ??
    //                 propertyInfo.PropertyInfo.PropertyType);
    //         }
    //
    //         var values = new object[props.Count()];
    //         foreach (var itemm in entityList)
    //         {
    //             for (var i = 0; i < values.Length; i++)
    //             {
    //                 values[i] = props[i].PropertyInfo.GetValue(itemm, null);
    //             }
    //
    //             table.Rows.Add(values);
    //         }
    //
    //         bulkCopy.WriteToServer(table);
    //     }
    // }

    #endregion
    
    #endregion
    
    
    
    #region Pager


    public PageResult<TP> Pager<TP>(string sql, DynamicParameters param, PageSearchModel search)
    {
        var pager = new PageResult<TP>();
        var countSql = $"Select Count(*) from ({sql}) c";
        pager.totalCount = DbConn.QueryFirst<int>(countSql, param);

        sql += $" order by {search.Sort} {search.Order} OFFSET {search.Offset} ROWS FETCH NEXT {search.Limit} ROWS ONLY";

        pager.rows = DbConn.Query<TP>(sql, param);

        return pager;
    }
    
    public PageResult<TP> Pager<TP>(string sql, DynamicParameters param, PageSearchModel2 search)
    {
        var pager = new PageResult<TP>();
        var countSql = $"Select Count(*) from ({sql}) c";
        pager.totalCount = DbConn.QueryFirst<int>(countSql, param);

        sql += $" order by {search.Sort} {search.Order} OFFSET {search.Offset} ROWS FETCH NEXT {search.Limit} ROWS ONLY";

        pager.rows = DbConn.Query<TP>(sql, param);

        return pager;
    }


    public async Task<PageResult<TP>> PagerAsync<TP>(string sql, DynamicParameters param, PageSearchModel search)
    {
        var pager = new PageResult<TP>();
        var countSql = $"Select Count(*) from ({sql}) c";
        pager.totalCount = DbConn.QueryFirst<int>(countSql, param);

        sql += $" order by {search.Sort} OFFSET {search.Offset} ROWS FETCH NEXT {search.Limit} ROWS ONLY";

        pager.rows = await DbConn.QueryAsync<TP>(sql, param);

        return pager;
    }
    
    public async Task<PageResult<TP>> PagerAsync<TP>(string sql, DynamicParameters param, PageSearchModel2 search)
    {
        var pager = new PageResult<TP>();
        var countSql = $"Select Count(*) from ({sql}) c";
        pager.totalCount = DbConn.QueryFirst<int>(countSql, param);

        sql += $" order by {search.Sort} OFFSET {search.Offset} ROWS FETCH NEXT {search.Limit} ROWS ONLY";

        pager.rows = await DbConn.QueryAsync<TP>(sql, param);

        return pager;
    }

    #endregion

    #region Cache

    public virtual void RemoveCache(T entity)
    {
    }
    #endregion

    #region IP
    //private IPUtility _ipUtility = null;
    public string GetIP()
    {
        //            if (_ipUtility == null)
        //            {
        //                _ipUtility = new IPUtility();
        //            }
        return "0.0.0.0";
    }
    #endregion

    #region 事物

    public TP Trans<TP>(Func<IDbTransaction, TP> func, IsolationLevel level = IsolationLevel.ReadCommitted)
    {
        var trans = DbConn.BeginTransaction(level);
        try
        {
            var result = func(trans);
            trans.Commit();
            return result;
        }
        catch (Exception ex)
        {
            trans.Rollback();
            Logger.LogError(ex.StackTrace);
            throw;
        }
        finally
        {
            trans.Dispose();
            trans = null;
        }
    }

    #endregion


    public void Dispose()
    {

        // if (
        //     != null)
        // {
        //     _dbConn.Close();
        //     _dbConn = null;
        // }
    }
}